package simtester.cloud

import com.amazonaws.auth.PropertiesCredentials
import com.amazonaws.services.ec2.AmazonEC2Client
import com.amazonaws.services.ec2.model.DescribeInstancesRequest
import com.amazonaws.services.ec2.model.RunInstancesRequest
import com.amazonaws.services.ec2.model.TerminateInstancesRequest
import com.amazonaws.AmazonServiceException
import com.gmongo.GMongo
import com.mongodb.DBRef
import groovy.time.TimeCategory

class Cloud {
    private static final STAGING_DIR = '/home/ec2-user/simtester'
    private static final TMP_DIR = "/tmp"
	private static final SRV_DIR = '/usr/share/tomcat6'
	private static final WAR_DIR = '/usr/share/tomcat6/webapps'
	private static final ROOT = '/'
	private type
    private env
    private ec2
    private key


    Cloud(args) {
        type = args.type ?: 'remote'
        env  = args.env ?: ''
        key  = args.key

        def creds = new PropertiesCredentials( new File(args.creds) )
        ec2       = new AmazonEC2Client(creds)
        ec2.setEndpoint(args.endpoint)
    }

    /**
     * Deploy the test agent. Accepts a map of named parameters
     * @param testrunId
     * @param script
     * @param agentpkg
     * @param url
     * @param repeats
     * @param testcaseId optional
     * @return ID to use for the TestRun
     */
    def createTestAgent(args) {
        def instance
        if (type == 'local') {
            def id = createLocalEnv(args)
            
            instance = [
                id: id,
                amiId: 'ami-123abc456',
                type: 'm1.large'
            ]
            
            if (args.script) {
                runLocalCmd('java -jar agent.jar &', id)
            }
        } else {
            instance = createRemoteEnv(args)
            
            if (args.script) {
                instance.execAsync("cd $STAGING_DIR/agent; java -jar agent.jar &")
            }
        }

        return instance
    }
    
    def createBlankTestAgent(args) {
        createTestAgent(args)
    }
    
    def setupTestAgent(args) {
        if (type == 'local') {
            setupLocalEnv(args)
            runLocalCmd('java -jar agent.jar &', args.instanceId)
            
            return [id: args.instanceId]
        } else {
            args.instance = findInstance(args.instanceId)
            setupRemoteEnv(args)
            args.instance.execAsync("cd $STAGING_DIR/agent; java -jar agent.jar &")
            return args.instance
        }
    }
    
    def removeInstances(instances) {
        if (instances.isEmpty()) {
            return
        }
        
        if (type == 'local') {
            instances.each { instance ->
                assert instance =~ /test\/testenv/  // Just a safeguard against accidentally removing things you don't want to
                new AntBuilder().delete(dir: instance)
            }
        } else {
            def terminateRequest = new TerminateInstancesRequest(instances as List)
            ec2.terminateInstances(terminateRequest)
        }
    }
    
    def createInstance(args) {
        try {
            def runInstancesRequest = new RunInstancesRequest()
                .withInstanceType(args.type)
                .withImageId(args.imageId)
                .withMinCount(1)
                .withMaxCount(1)
                .withSecurityGroups('cmpe295-security-group')
                .withKeyName('cmpe295-keypair')
            
            def runInstancesResult = ec2.runInstances(runInstancesRequest)
            def newInstance = runInstancesResult.reservation.instances[0]
            return new CloudInstance(cloud: this,
                                     id: newInstance.instanceId,
                                     publicDnsName: newInstance.publicDnsName,
                                     publicIpAddress: newInstance.publicIpAddress,
                                     state: newInstance.state.name,
                                     amiId: newInstance.imageId,
                                     type: newInstance.instanceType,
                                     key: key)
        } catch (AmazonServiceException ase) {
            throw new CloudException(ase.message)
        }
    }
    
    def findInstance(instanceId) throws CloudException {
        try {
            def request = new DescribeInstancesRequest()
            request.instanceIds = [instanceId]
            
            def searchResults = ec2.describeInstances(request)
            def instance = searchResults.reservations[0].instances[0]

            if (instance.instanceId) {
                return new CloudInstance(cloud: this,
                                         id: instance.instanceId,
                                         publicDnsName: instance.publicDnsName,
                                         publicIpAddress: instance.publicIpAddress,
                                         state: instance.state.name,
                                         key: key)
            }
            
        } catch (AmazonServiceException ase) {
            if (ase.errorCode == 'InvalidInstanceID.NotFound') {
                // Do nothing
            } else {
                throw new CloudException(ase.message)
            }
        }
        
        return null
    }
    
    
    private def createRemoteEnv(args) {
        //def instance = createInstance(type: 'm1.small', imageId: 'ami-11d68a54')
        def instance = createInstance(type: 'c1.medium', imageId: 'ami-1bd68a5e')

        //Utils.waitFor(limit: 60000, step: 5000) { instance.state == 'running' }

        setupAgentDir(instance, args.agentpkg)

        if (args.script) {
            def setupArgs = args
            setupArgs.instance = instance
            setupRemoteEnv(setupArgs)
        }
        
        return instance
    }
    
    private setupRemoteEnv(args) {
        args.instance.exec("rm -f $STAGING_DIR/agent/agent.log $STAGING_DIR/agent/commands.txt $STAGING_DIR/agent/finished")
        
        generateTestScriptFile(args.instance, args.script)
        updateAgentConfig(instance:  args.instance,
                          url:       args.url,
                          id:        args.testrunId,
                          tcId:      args.testcaseId,
                          repeats:   args.repeats,
                          duration:  args.duration,
                          firstuser: args.firstuser,
                          users:     args.users,
                          
                          peruser:   args.peruser,
                          per:       args.per,
                          totalduration: args.totalduration,
                          delay:     args.delay)
    }

    private setupAgentDir(instance, agentPackage) {
        def agentDir = "$STAGING_DIR/agent"
        instance.exec("mkdir -p $agentDir")
        instance.sendFile(agentPackage, to: agentDir)
        
        return agentDir
    }
    
    private void generateTestScriptFile(instance, script) {
        def scriptfile = new File("$TMP_DIR/script-$instance.id")
        scriptfile.write(script)

        def dir = "$STAGING_DIR/agent"
        instance.sendFile(scriptfile, to: dir)
        instance.exec("mv -f $dir/$scriptfile.name $dir/AgentScript.groovy")
        scriptfile.delete()
    }
    
    private void updateAgentConfig(args) {
        def configText = buildConfigText(args)
        
        def configFile = "$STAGING_DIR/agent/AgentConfig.groovy"
        args.instance.exec("echo \"$configText\" > $configFile")
    }
    
    private def createLocalEnv(args) {
        def instanceName
        def count = 1
        while (true) {
            instanceName = "$env/agent$count"
            def file = new File(instanceName)
            if (!file.exists()) {
                new File(instanceName).mkdirs()
                break;
            }
            count++
        }

        grabAgent(instanceName, args.agentpkg)
        
        if (args.script) {
            def setupArgs = args
            setupArgs.instanceId = instanceName
            setupLocalEnv(setupArgs)
        }
        
        return instanceName
    }
    
    private void grabAgent(dir, agentPackage) {
        new AntBuilder().copy(file: agentPackage.absolutePath, todir: dir)
    }
    
    private void setupLocalEnv(args) {
        def filesToDelete = ['agent.log', 'commands.txt', 'finished', 'AgentConfig.groovy', 'AgentScript.groovy']
        filesToDelete.each {
            new File("$args.instanceId/$it").delete()
        }
        
        makeTestScriptFile(args.instanceId, args.script)
        makeAgentConfig(dir:       args.instanceId,
                        url:       args.url,
                        id:        args.testrunId,
                        tcId:      args.testcaseId,
                        repeats:   args.repeats,
                        duration:  args.duration,
                        firstuser: args.firstuser,
                        users:     args.users,
                        
                        peruser:   args.peruser,
                        per:       args.per,
                        totalduration: args.totalduration,
                        delay:     args.delay)
    }

    private void makeTestScriptFile(dir, script) {
        def file = new File("$dir/AgentScript.groovy")
        file.createNewFile()
        file.write(script)
    }

    private void makeAgentConfig(args) {
        def configText = buildConfigText(args)
        
        def file = new File("$args.dir/AgentConfig.groovy")
        file.createNewFile()
        file.write(configText)
    }
    
    private buildConfigText(args) {
        def configText = "serverurl = '$args.url'\ntestrunid = '$args.id'\n"
        
        if (args.tcId) {
            configText += "testcaseid = '$args.tcId'\n"
        }
        
        if (args.duration) {
            configText += "duration = $args.duration\n"
        } else {
            def repeats = args.repeats ?: 1
            configText += "repeats = $repeats\n"
        }
        
        if (args.firstuser) {
            configText += "firstuser = $args.firstuser\n"
        }
        
        if (args.users) {
            configText += "users = $args.users\n"
        }
        
        if (args.peruser) {
            configText += "peruser = $args.peruser\n"
            configText += "per = $args.per\n"
            configText += "totalduration = $args.totalduration\n"
            configText += "delay = $args.delay\n"
        }
        
        return configText
    }
    
    private void runLocalCmd(String cmd, String dir) {
        new File("$dir/commands.txt") << "$cmd\n"
        def proc = cmd.execute(null, new File(dir))
    }
	
	def createServer(args) {
		return setupServerEnv(args.srvpkg, args.credkey, args.kp, args.confFile)
	}
	
	private void updateCloudConfig(instance) {
		def publicDNSname = instance.publicDnsName
		def configText = "serverURL = '$publicDNSname'\n"
		def configFile = new File("conf/SimTesterConfig.groovy")
		
		def text = configFile.text
		text = text.replaceAll(~"http://(.+):8080"){ 
			"http://$publicDNSname:8080"
			}
		configFile.write(text)
	}
	
	private setupFiles(instance, credentials, keypair, configFile) {
		instance.sendFile(credentials, to: '/home/ec2-user')
		instance.sendFile(keypair, to: '/home/ec2-user')
		instance.sendFile(configFile, to: '/home/ec2-user')
		instance.exec("sudo mv $credentials.name $ROOT")
		instance.exec("sudo mv $keypair.name $ROOT")
		instance.exec("sudo mv $configFile.name $SRV_DIR")
    }
	
	private void setupMongoDB(instance){
		def dnsname = instance.publicDnsName
		def db = new GMongo(dnsname, 27017).getDB("sim-prod")
		db.addUser("simuser","simuser".toCharArray())
	}
	
	def setupServerEnv(srvPackage, credentials, keypair, configFile) {
		def instance = createInstance(type: 'm1.large', imageId: 'ami-1bd68a5e')
        //def instance = createInstance(type: 'm1.large', imageId: 'ami-e78cd4a2')

        def starttime = new Date()
        
        def t = Thread.start {
            instance.sendFile(srvPackage, to: '/home/ec2-user')
            instance.exec("sudo mv $srvPackage.name $WAR_DIR")
		}
        
		//  - wget to get mongodb
		instance.exec("wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.0.2.tgz")
		
		//  - create mongodb directories and move tgz file to mongodb directory
		instance.exec("mkdir -p mongodb/db;mv ~/mongodb-linux-x86_64-2.0.2.tgz ~/mongodb")
		
		//  - unzip tar file
		instance.exec("tar xf ~/mongodb/mongodb-linux-x86_64-2.0.2.tgz -C ~/mongodb")
		
		//  - start database and create tables?
		instance.execAsync("./mongodb/mongodb-linux-x86_64-2.0.2/bin/mongod --dbpath ~/mongodb/db &")
		
		//  - install tomcat
		instance.exec("sudo yum install tomcat6 -y")

		//- update config file
		updateCloudConfig(instance)

		//  - copy war file, credential file, keypair file
		setupFiles(instance, credentials, keypair, configFile)
		
		setupMongoDB(instance)
        
        t.join()
		
		//  - start tomcat
		instance.exec("sudo service tomcat6 start")
        
        def stoptime = new Date()
        def elapsed = TimeCategory.minus(stoptime, starttime)
        println "Server deployed in $elapsed"
	
		return instance
	}
}
